/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		sdeditg main.cpp
 * PURPOSE:		main() function and support
 *				for SD Edit GUI
 */

struct TOP_NOTIFY_CONTEXT
{
	ObjectTypes::Object *Obj;
	UCHAR Privileges;
};

static ULONG_PTR CALLBACK RecieveNotify(PVOID Context, DIALOG_NOTIFY_OPERATION Operation, ULONG_PTR Data)
{
	TOP_NOTIFY_CONTEXT *Context2 = reinterpret_cast<TOP_NOTIFY_CONTEXT *>(Context);

	switch(Operation)
	{
	case DN_APPLY:
	case DN_CLOSE_OK:
		{
			SD_EDIT_STATE &NewState = *reinterpret_cast<SD_EDIT_STATE *>(Data);

			Context2->Obj->SetTreeEditState(NewState, Context2->Privileges);
		}
		break;

	case DN_QUERY_INHERITANCE:
		{
			DN_QUERY_INHERITANCE_ES &Child = *reinterpret_cast<DN_QUERY_INHERITANCE_ES *>(Data);

			auto_refct<ObjectTypes::Object> Parent = Context2->Obj->OpenParent(Context2->Privileges);

			if(!Parent)
				return 0;

			SD_EDIT_STATE ParentEs;

			Parent->QueryTreeEditState(ParentEs, Context2->Privileges);
			
			ParentEs.Dacl.State |= (Child.Es.Dacl.State & ACL_MERGE_MODE_BITS) | ACL_FLAG_CHANGED; //ACL_FLAG_CHANGED, otherwise nothing will be done in the merge
			ParentEs.Sacl.State |= (Child.Es.Sacl.State & ACL_MERGE_MODE_BITS) | ACL_FLAG_CHANGED;

			//Child.Es = ParentEs;

			SdMerge::MergeEditState(ParentEs, Child.Es, Context2->Obj->GetGenericMapping());
		}
		break;
	}

	if(Operation <= DN_CLOSE_MAX)
		PostQuitMessage(0);

	return 0;
}

int SdEditWinMainGuarded()
{
	SdEditThreadResources SdeTr;
	auto_refct<GuiLibResources> Glr(ReferenceGlr());
	CommandInstance Ci(GetCommandLine());
	Ci.FinishInit();

	//if none of these, sd edit has nothing to do: show help message
	if(!(Ci.GlobalOpts.Opts[GoiType].Flags & SDE_OPT_SPECIFIED ||
	     Ci.GlobalOpts.Opts[GoiTypeClass].Flags & SDE_OPT_SPECIFIED ||
		 Ci.GlobalOpts.Opts[GoiShellinstPack].SubList->Opts[Shellinst::OiInstall].Flags & SDE_OPT_SPECIFIED ||
		 Ci.GlobalOpts.Opts[GoiShellinstPack].SubList->Opts[Shellinst::OiUninstall].Flags & SDE_OPT_SPECIFIED))
	{
		PCWSTR Message =
			L"sdedit.exe t <type> n <name> [h <host>]\n\nPossible values for type include\n\tfile\n\tkey\n\tservice\n\tntds\n\nSee sdedit.chm for more types and options";
		
		MessageBox(NULL, Message, L"SD Edit basic usage", MB_OK);

		return 0;
	}

	{
		ModifyPrivs Mp(Ci.GlobalOpts.Opts[GoiPrivPack].SubList);

		//open specified object
		ModuleResourceAccumulator ModRes;
		BOOLEAN HadPossible = FALSE;
		BOOLEAN SubjectOpened = FALSE;
		BOOLEAN HadInvalid = FALSE;
		BOOLEAN HadSupported = FALSE;

		for(ULONG i = 0; i < RTL_NUMBER_OF(Ci.Modules); i++)
		{
			Module::OPEN_STATUS OpenStatus = 0;

			if(Ci.GlobalOpts.Opts[GoiTypeClass].Flags & SDE_OPT_SPECIFIED)
			{
				BOOLEAN Selected = _wcsicmp(Ci.Modules[i]->ModuleName(), Ci.GlobalOpts.Opts[GoiTypeClass].String) == 0;

				AddResourcesFrom(OpenStatus, Ci.Modules[i], Ci.GenericOpts, ModRes, Mp.Privileges, Selected);

				//in case the user decided to coninue and there was an error that didn't make it obvious the entire process had failed
				if(Selected)
					switch(OpenStatus & Module::OBJECT_OPEN_MASK)
					{
					case Module::OBJECT_OPENED:
						SubjectOpened = TRUE;
						break;
					
					//in these cases, abort since the user specified module couldn't open the object, there's no point in continuing
					case Module::OBJECT_NAME_INVALID:
						RaiseTextError(L"Subject name invalid. Check input string.", Ci.GenericOpts.ObjName, Ci.Modules[i]->ModuleName(), NULL, TRUE);
						return 1;

					case Module::OBJECT_OPEN_FAIL:
						if(OpenStatus & Module::SECURITY_FAILURE)
							RaiseTextError(L"Security failure trying to open the subject.", Ci.GenericOpts.ObjName, Ci.Modules[i]->ModuleName(), NULL, TRUE);
						else
							RaiseTextError(L"Failed to open subject using a specific module.", Ci.GenericOpts.ObjName, Ci.Modules[i]->ModuleName(), NULL, TRUE);
						return 1;

					case Module::OBJECT_UNKNOWN_TYPE:
						RaiseTextError(L"Unknown type for module", Ci.GenericOpts.ObjType, Ci.Modules[i]->ModuleName(), NULL, TRUE);
						return 1;
					}
			}
			else //no specific tc specified, try each until subject opened
				AddResourcesFrom(OpenStatus, Ci.Modules[i], Ci.GenericOpts, ModRes, Mp.Privileges, !SubjectOpened);

			switch(OpenStatus & Module::OBJECT_OPEN_MASK)
			{
			case Module::OBJECT_OPENED:
				SubjectOpened = TRUE;
				break;
			
			case Module::OBJECT_NAME_NOTFOUND:
				HadPossible = TRUE;
				HadSupported = TRUE;
				break;

			case Module::OBJECT_NAME_INVALID:
				HadInvalid = TRUE;
				HadSupported = TRUE;
				break;
			
			case Module::OBJECT_OPEN_FAIL:
				//abort since one of the modules recognized the object but specifically couldn't open it. (e.g. due to access denied)
				if(OpenStatus & Module::SECURITY_FAILURE)
					RaiseTextError(L"Security failure trying to open the subject.", Ci.GenericOpts.ObjName, Ci.Modules[i]->ModuleName(), NULL, TRUE);
				else
					RaiseTextError(L"Failed to open subject using a default module.", Ci.GenericOpts.ObjName, Ci.Modules[i]->ModuleName(), NULL, TRUE);
				return 1;

			case Module::OBJECT_UNKNOWN_TYPE:
				break;
			}
		}

		if(Ci.GlobalOpts.Opts[GoiShellinstPack].SubList->Opts[Shellinst::OiInstall].Flags & SDE_OPT_SPECIFIED ||
		   Ci.GlobalOpts.Opts[GoiShellinstPack].SubList->Opts[Shellinst::OiUninstall].Flags & SDE_OPT_SPECIFIED)
			return 0; //they really watned to install instead


		if(!ModRes.Subject || !SubjectOpened)
		{
			if(!HadSupported)
				RaiseTextError(L"Invalid type or type class. No modules supported the specified type.", Ci.GenericOpts.ObjType, NULL, NULL, TRUE);

			if(HadPossible && !Ci.GenericOpts.ObjType)
				RaiseTextError(L"Subject could not be found for any default type.", L"Try specifying a specific type with the 't' option.", NULL, NULL, TRUE);

			if(HadPossible)
				RaiseTextError(L"Subject could not be found with that type. Is the type and object name correct?", Ci.GenericOpts.ObjName, NULL, NULL, TRUE);

			if(HadInvalid)
				RaiseTextError(L"Subject name was not valid for any default type; it may contain invalid characters.", L"Try specifying a specific type with the 't' option or correcting the name.", NULL, NULL, TRUE);

			//in any case, things didn't work out
			return 1;
		}

		auto_refct<ObjectTypes::Object> Obj = ModRes.Subject;

		SD_EDIT_STATE Es;

		Obj->QueryTreeEditState(Es, Mp.Privileges);

		TOP_NOTIFY_CONTEXT Nc;
		Nc.Obj = Obj;
		Nc.Privileges = Mp.Privileges;

		PCWSTR Title;
		WCHAR TitleBuff[MAX_STRING_CHARS];

		if(Ci.GenericOpts.Host)
		{
			swprintf_s(TitleBuff, L"%s on %s", Obj->Name, Ci.GenericOpts.Host);
			Title = TitleBuff;
		}
		else
			Title = Obj->Name;

		auto_refct<SdEditor::WindowObject> wob = new SdEditor::WindowObject(*Ci.GuiPrefs, Obj->SpecificHeaders, Es, Title, &ModRes.Translators, &ModRes.Enumerators, Obj->PathComponents, NULL, &Nc, &RecieveNotify);

		(wob);

		MessagePump();
	}

	return 0;
}

int __stdcall SdEditWinMain()
{
	int ret = 0;

	__try
	{
		ret = SdEditWinMainGuarded();
	}
	__except(ShowErrorMessageBox(exception_info(), NULL))
	{}

	//DBG_CheckAllocs(); //use with overheap.cpp to detect leaks

	return ret;
}